'''
$LastChangedBy: csgillespie $
$LastChangedDate: 2007-06-06 11:08:06 +0000 (Wed, 06 Jun 2007) $
Colin Gillespie (c.gillespie@ncl.ac.uk)

A wrapper around a BASIS ws.
'''
import re, sys
import copy

from GlobalVariables.ErrorClasses import EditError
from GlobalVariables.GlobalAttributes import GlobalParameters

def prettyPrintTable(headings, data=None):
	'''A function to pretty print tables'''

	names, lengths, rules = [], [], []
	field_length = int(110/len(headings))
	for head in headings:
		head = head.ljust(field_length)
		names.append(head)
		lengths.append(field_length)
		rules.append('-' * field_length)
	format = " ".join(["%%.%ss" % l for l in lengths])
	result = [format % tuple(names), format % tuple(rules)]

	for row in data:
		row = [str(r).ljust(field_length) for r in row]

		result.append(format % tuple(row))
	return "\n".join(result)

class BaseBasisWebServicesClass(GlobalParameters):
	'''A base class for the classes below'''
	def __init__(self, **kwargs):
		'''Get all the getters/setters methods from AlterConnectToBasis'''
		GlobalParameters.__init__(self, **kwargs)

class ModelTable(BaseBasisWebServicesClass):
	'''Gets the private & public model tables'''
	def getMyModelInfo(self, pretty_print = True):
		'''Gets the private model table'''
		details = ('modelUrn', 'modelName', 'noOfSims', 'dateEntered')
		model_info = self.s.getMyModelInfo(self.s_id)
		data = []
		for row in model_info:
			data_row = []
			for info in row:
				data_row.append(info)
			data.append(data_row)
		if pretty_print:
			return prettyPrintTable(details, data)
		else:
			return data

	def getPublicModelInfo(self, pretty_print = True):
		'''Gets the public model table'''
		details = ('modelUrn', 'modelName', 'author', 'dateEntered', \
			'noOfSims', 'haveCopy')
		model_info = self.s.getPublicModelInfo(self.s_id)
		data = []
		for row in model_info:
			data_row = []
			for info in row:
				data_row.append(info)
			data.append(data_row)
		if pretty_print:
			return prettyPrintTable(details, data)
		else:
			return data

class Model(BaseBasisWebServicesClass, dict):
	'''Deals with the getting, putting and saving of models'''

	def __init__(self, **kwargs):
		'''Get all the getters/setters methods from AlterConnectToBasis'''
		BaseBasisWebServicesClass.__init__(self, **kwargs)
		self.details_dict = {'modelUrn':'Model urn:', 'modelName':'Model name', 
			'modelNotes':'Model notes', 'dateEntered':'Date entered',\
			'publicStatus':'Public status', 'author':'Author', 
			'noOfSims':'No. of Simulations'}
		self.private_details_return = ('modelUrn', 'modelName', 'modelNotes',
			'dateEntered','publicStatus', 'author','noOfSims')
		if self.model_urn:
			self.__setDetails(self.model_urn)


	def __setDetails(self, model_urn=None):
		print model_urn
		if not model_urn:
			self.__basis_edit = 0	
			for detail in self.details_dict.keys():
				self[detail] = None
			self.__basis_edit = 1
			return
		#Try to get the private model details, then try public, then give up.
		try:
			details = self.s.getMyModelDetails(self.s_id, model_urn)
			print 'XXXXXXXXX', self.s.getMyModelDetails(self.s_id, model_urn)
		except:
			details = self.s.getPublicModelDetails(model_urn)
			#Insert public status into public model
			details.insert(4, 'public')
		
		sys.exit()
		#__basis_edit indicatesif variable can be changed.
		self.__basis_edit = 0	
		for i, detail in enumerate(self.private_details_return):
			self[detail] = str(details[i])
		self.__basis_edit = 1

	def __setitem__(self, key, item):
		#Initialising the variables
		if not self.__basis_edit:
			dict.__setitem__(self, key, item)
			return
		if not self.model_urn:
			print "Please set the model_urn you are interested in"
			return 
		#Change the values that can be altered.
		if key == 'modelName':
			
			if re.search('^[a-zA-Z_]([\w|_]*)$',item):
				self.s.updateModelDetails(self.s_id, self['modelUrn'], item, \
				self['modelNotes'] )
			else:
				raise EditError('''Model name not a proper SBML id. 
				Try removing spaces and special characters''')
		elif key == 'modelNotes':
			self.s.updateModelDetails(self.s_id, self['modelUrn'], self['modelName'], \
				item)
		elif key == 'publicStatus':
			#TODO Should also raise warning
			self.s.updatePublicStatus(self.s_id, self['modelUrn'], item)
		elif key == 'modelUrn':
			self.getModel(item)
			self.__setDetails(item)
		elif key in ('author', 'noOfSims', 'dateEntered'):
			raise EditError('%s can not be edited by a user' % key)
		else:
			raise EditError('unknown parameter %s' % key)
		
		dict.__setitem__(self, key, item)

	def getModel(self, model_urn):
		'''Returns the SBML of a model'''

		try:
			sbml = str(self.s.getSBML(self.s_id, model_urn))
		except SOAPpy.Types.faultType:
			sbml = str(self.s.getPublicSBML(model_urn))

		self.sbml = sbml
		self.model_urn = model_urn
		self.__setDetails(model_urn)
		return True		

	def deleteModel(self):
		'''Deletes the model from the database. All simulation data is also deleted.'''
		self.s.delSBML(self.s_id, self.model_urn)
		self.model_urn = None
		self.sbml = None
		self.__setDetails()
		return True

	def copyModel(self, model_urn):
		'''Copies a public model into you private space'''
		self.s.copySBML(self.s_id, model_urn)
		self.model_urn = model_urn
		self.__setDetails(model_urn)
		return True

	def __createRevision(self):
		'''Not done yet'''
		pass
		#old_urn = self.model_urn
		#self.model_urn = self.s.putSBML(self.s_id, self.sbml)
		#TODO, should add r1 to the name
		#return True

	def putModel(self):
		'''Puts a model into the database'''
		self.model_urn = str(self.s.putSBML(self.s_id, self.sbml))
		print 'Model_urn=====', self.model_urn

	def saveModel(self):
		'''Saves a model to an existing model_urn'''
		#If don't have a model_urn then create a new one
		if not self.model_urn:
			self.model_urn = str(self.s.putSBML(self.s_id, self.sbml))
			self.__setDetails(self.model_urn)
		else:
			try:
				self.s.replaceAllSBML(self.s_id, self.model_urn, self.sbml)
			except:
				print 'Error: is the model public? \
				Does it have simulations attached to it?'
		return True

class UserDetails(BaseBasisWebServicesClass, dict):
	'''Deals with username/passwords/email address.
	Treats the details as a dictionary'''

	def __init__(self, **kwargs):
		'''Get all the getters/setters methods from AlterConnectToBasis'''
		BaseBasisWebServicesClass.__init__(self, **kwargs)
		details = self.s.getUserDetails(self.s_id)
		self.basis_edit = 0
		for heading, detail in zip(['username', 'fullName', 'emailAddress'], details):
			self[heading] = str(detail)
		self['password'] = self.password
		self.basis_edit = 1


	def __setitem__(self, key, value):
		#Initialising the variables
		if not self.basis_edit:
			dict.__setitem__(self, key, value)
			return

		#Change the values that can be altered.
		if key == 'fullName':
			self.s.updateDetails(self.s_id, value, self['emailAddress'])
		elif key == 'emailAddress':
			self.s.updateDetails(self.s_id, self['fullName'], value)
		elif key == 'password':
			self.s.updatePassword(self.s_id, value, value)
			self.password = value
		elif key in ('username'):
			raise EditError('You can not change your username')
		else:
			raise EditError('unknown parameter %s' % key)

		dict.__setitem__(self, key, value)

class Simulate(BaseBasisWebServicesClass):
	'''Access to the BASIS simulator'''
	
	def simulate(self, run_name, max_time, no_of_sims, no_of_iters):
		'''Set of a simulation'''
		if not self.model_urn:
			print 'Please set a model_urn'
			return
		group_urn = self.s.simulate(self.s_id, self.model_urn, run_name, max_time, 
							no_of_sims, no_of_iters)
		self.simulation_group_urn = group_urn
		print 'Simulation %s is running' % group_urn

	def killSimulation(self):
		'''Stops a simulation. Does not delete any data.'''
		if not self.simulation_group_urn:
			print 'Please set a simulation_group_urn'
			return
		self.s.killSimulation(self.s_id, self.simulation_group_urn)
		return True

	def deleteSimulation(self):
		'''Deletes all simulation data for that simulation Urn.
			Will stop any simulations that are running.'''
		if not self.simulation_group_urn:
			print 'Please set a simulation_group_urn'
			return
		self.s.delSimulation(self.s_id, self.simulation_group_urn)
		self.simulation_group_urn = None
		return True

	def getSimulationData(self, pretty_print = True):
		'''Gets the simulation data for that urn'''
		if not self.model_urn:
			print 'Please set a model_urn'
			return
		data = self.s.getMySimulationInfoByUrn(self.s_id, self.model_urn)
		headings = ('simulationUrn', 'runName',  'simsCompleted', 'jobStatus')
		if pretty_print:
			return prettyPrintTable(headings, data)	
		sim_data_list = []
		for row in data:
			sim_data_list.append([str(value) for value in row])
		return sim_data_list
		

	def getListOfSpecies(self):
		'''Get a list of species for a simulation'''
		if not self.simulation_group_urn:
			print 'Please set a simulation_group_urn'
			return
		species  = self.s.getMySpeciesInfo(self.s_id, self.simulation_group_urn)
		return [str(sp) for sp in species]

	def getSimDetails(self, pretty_print = True):
		if not self.simulation_group_urn:
			print 'Please set a simulation_group_urn'
			return
		details = self.s.getMySimulationGroupInfo(self.s_id, self.simulation_group_urn)
		headings = ( 'Run name', 'model urn', 'Model Name',  'Max Time', \
			'No. of Runs', 'Iterations', 'Job Status','Errors')
		
		if pretty_print:
			table = ''
			for detail, heading in zip(details, headings):
				table += str(heading) + '\t' + str(detail) + '\n'
			return table
		return [str(detail) for detail in details]
		
	
	def getData(self, dir='/tmp/'):
		'''Get all the simulation data and put it in a file'''
		
		#A bit messy, bit really just changin 1(2) to 2
		no_of_sims = int(self.getSimDetails(pretty_print = False)[4].split('(')[1][:-1])
		if not no_of_sims:
			print 'There are no simulations for %s' % self.simulation_group_urn
			return	
		list_of_species = self.getListOfSpecies()
		
		sim_urn = self.simulation_group_urn + ':' + str(1)
		time_data = self.s.getMySpecieData(self.s_id, sim_urn, 'time')
		time = [[str(t)] for t in time_data]
		
		for sim in xrange(no_of_sims):
			print 'Getting simulation %s' % str(sim+1)
			sim_urn = self.simulation_group_urn + ':' + str(sim+1)
			fp = open(dir + sim_urn + '.txt', 'w')
			data = copy.deepcopy(time)
			
			for sp_id in list_of_species:
				if sp_id != 'time':
					sp_data = self.s.getMySpecieData(self.s_id, sim_urn, sp_id)
					for i, sp_dat in enumerate(sp_data):
						data[i].append(str(sp_dat))
			for row in data:
				fp.write("\t".join(row))
				fp.write("\n")
			fp.close()
			

if __name__ == '__main__':
	print prettyPrintTable(('a', 'b', 'c'), [[1, 2, 3]])
